using System;

namespace FPLibrary
{
	public struct FPBounds : IEquatable<FPBounds>, IFormattable
	{
		private FPVector3 _center;
		private FPVector3 _extents;

		public FPBounds(FPVector3 center, FPVector3 size)
		{
			_center  = center;
			_extents = size * 0.5F;
		}

		// The center of the bounding box.
		public FPVector3 center
		{
			get => _center;
			set => _center = value;
		}

		// The total size of the box. This is always twice as large as the ::ref::extents.
		public FPVector3 size
		{
			get => _extents * 2.0F;
			set => _extents = value * 0.5F;
		}

		// The extents of the box. This is always half of the ::ref::size.
		public FPVector3 extents
		{
			get => _extents;
			set => _extents = value;
		}

		public Fix64 extentsValue => _extents.x + _extents.y;

		// The minimal point of the box. This is always equal to ''center-extents''.
		public FPVector3 min
		{
			get => center - extents;
			set => SetMinMax(value, max);
		}

		// The maximal point of the box. This is always equal to ''center+extents''.
		public FPVector3 max
		{
			get => center + extents;
			set => SetMinMax(min, value);
		}

		public override int GetHashCode() =>
			center.GetHashCode() ^ (extents.GetHashCode() << 2);

		public override bool Equals(object other)
		{
			if (!(other is FPBounds))
				return false;

			return Equals((FPBounds)other);
		}

		public bool Equals(FPBounds other) =>
			center.Equals(other.center) && extents.Equals(other.extents);

		//*undoc*
		public static bool operator ==(FPBounds lhs, FPBounds rhs) =>

			// Returns false in the presence of NaN values.
			(lhs.center == rhs.center && lhs.extents == rhs.extents);

		//*undoc*
		public static bool operator !=(FPBounds lhs, FPBounds rhs) =>

			// Returns true in the presence of NaN values.
			!(lhs == rhs);

		// Sets the bounds to the /min/ and /max/ value of the box.
		public void SetMinMax(FPVector3 min, FPVector3 max)
		{
			extents = (max - min) * 0.5F;
			center  = min + extents;
		}

		// Grows the Bounds to include the /point/.
		public void Encapsulate(FPVector3 point) =>
			SetMinMax(FPVector3.Min(min, point), FPVector3.Max(max, point));

		public void Encapsulate(FPVector2 point) =>
			SetMinMax(FPVector3.Min(min, point.ToFPVector3()), FPVector3.Max(max, point.ToFPVector3()));

		// Grows the Bounds to include the /Bounds/.
		public void Encapsulate(FPBounds fpBounds)
		{
			Encapsulate(fpBounds.center - fpBounds.extents);
			Encapsulate(fpBounds.center + fpBounds.extents);
		}

		// Expand the bounds by increasing its /size/ by /amount/ along each side.
		public void Expand(float amount)
		{
			amount  *= .5f;
			extents += new FPVector3(amount, amount, amount);
		}

		// Expand the bounds by increasing its /size/ by /amount/ along each side.
		public void Expand(FPVector3 amount) =>
			extents += amount * .5f;

		// Does another bounding box intersect with this bounding box?
		public bool Intersects(FPBounds fpBounds) =>
			(min.x <= fpBounds.max.x) && (max.x >= fpBounds.min.x) &&
			(min.y <= fpBounds.max.y) && (max.y >= fpBounds.min.y) &&
			(min.z <= fpBounds.max.z) && (max.z >= fpBounds.min.z);

		public bool Contains(FPVector3 position) =>
			position.x    >= min.x
			&& position.y >= min.y
			&& position.z >= min.z
			&& position.x < max.x
			&& position.y < max.y
			&& position.z < max.z;

		public bool Contains(FPVector2 position) =>
			position.x    >= min.x
			&& position.y >= min.y
			&& position.x < max.x
			&& position.y < max.y;

		public string ToString(string format, IFormatProvider formatProvider) =>
			$"Center: {(object)center.ToString()}, Extents: {(object)extents.ToString()}";
	}
}
